<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" >

<title>手写Promise-1 核心 | Zero2key&#39;s Blog</title>
<meta name="description" content="">

<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">

<link rel="shortcut icon" href="https://zero2key.gitee.io/favicon.ico">
<link rel="stylesheet" href="https://zero2key.gitee.io/styles/main.css">


  
    <link rel="stylesheet" href="https://unpkg.com/gitalk/dist/gitalk.css" />
  

  


<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://cdn.bootcss.com/highlight.js/9.12.0/highlight.min.js"></script>

<link rel="stylesheet" href="https://unpkg.com/aos@next/dist/aos.css" />


  </head>
  <body>
    <div id="app" class="main">

      <div class="sidebar" :class="{ 'full-height': menuVisible }">
  <div class="top-container" data-aos="fade-right">
    <div class="top-header-container">
      <a class="site-title-container" href="https://zero2key.gitee.io">
        <img src="https://zero2key.gitee.io/images/avatar.png" class="site-logo">
        <h1 class="site-title">Zero2key&#39;s Blog</h1>
      </a>
      <div class="menu-btn" @click="menuVisible = !menuVisible">
        <div class="line"></div>
      </div>
    </div>
    <div>
      
        
          <a href="/" class="site-nav">
            首页
          </a>
        
      
        
          <a href="/archives" class="site-nav">
            归档
          </a>
        
      
        
          <a href="/tags" class="site-nav">
            标签
          </a>
        
      
    </div>
  </div>
  <div class="bottom-container" data-aos="flip-up" data-aos-offset="0">
    <div class="site-description">
      
    </div>
    <div class="site-footer">
      Powered by <a href="https://github.com/getgridea/gridea" target="_blank" style="color:#fff;">Gridea</a> And <a style="display: inline-block;
    width: 80px;line-height: 12px;font-size: 12px;position: relative;" href="https://www.upyun.com/?utm_source=lianmeng&utm_medium=referral" target="_blank">&nbsp;<img style="height: 40px;position: absolute;left:0;top:-15px;" src=""></img></a>
    </div>
  </div>
</div>


      <div class="main-container">
        <div class="content-container" data-aos="fade-up">
          <div class="post-detail">
            <h2 class="post-title">手写Promise-1 核心</h2>
            <div class="post-date">2019-10-08</div>
            
            <div class="post-content">
              <h2 id="promise-简单实现">Promise 简单实现</h2>
<pre><code class="language-js">const PromiseStatusMap = {
  pending: 'pending',
  resolved: 'resolved',
  rejected: 'rejected'
};
</code></pre>
<p>Promise 有三种状态 <code>pending</code>、<code>resolved</code>、<code>rejected</code>，初始为 <code>pending</code>，会转变为 <code>resolved</code> 或 <code>rejected</code></p>
<pre><code class="language-js">const PromiseStatus = '[[PromiseStatus]]';
const PromiseValue = '[[PromiseValue]]';
</code></pre>
<p>Promise 的 <code>[[PromiseStatus]]</code> 属性用于存贮状态，<code>[[PromiseValue]]</code> 属性用于存储 <code>resolved</code> 或 <code>rejected</code> 的值</p>
<pre><code class="language-js">class P {
  constructor(executor) {
    this[PromiseStatus] = PromiseStatusMap.pending;
    this[PromiseValue] = undefined;

    const reject = reason =&gt; {
      if (this[PromiseStatus] === PromiseStatusMap.pending) {
        this[PromiseValue] = reason;
        this[PromiseStatus] = PromiseStatusMap.rejected;
      }
    };

    const resolve = value =&gt; {
      if (value &amp;&amp; value.then &amp;&amp; typeof value.then === 'function') {
        value.then(resolve, reject);
        return;
      }
      if (this[PromiseStatus] === PromiseStatusMap.pending) {
        this[PromiseValue] = value;
        this[PromiseStatus] = PromiseStatusMap.resolved;
      }
    };

    executor(resolve, reject);
  }
}
</code></pre>
<p>手写一个 Promise <code>P</code> 先看构造函数，构造函数中的内部函数 <code>resolve</code>、<code>reject</code> 修改 <code>P</code> 的状态并且将 <code>resolved</code> 或 <code>rejected</code> 的值存下来，仅修改 <code>pending</code> 状态的 <code>P</code>，修改一次后再次调用 <code>resolve</code> 或 <code>reject</code> 无效。</p>
<p>如果 <code>resolve</code> 的值是 <code>thenable</code> 的，直接调用 <code>value.then</code>，传入 <code>resolve</code>、<code>reject</code>。</p>
<pre><code class="language-js">then(onfulfilled, onrejected) {
  if (this[PromiseStatus] === PromiseStatusMap.resolved &amp;&amp; onfulfilled) {
    onfulfilled(this[PromiseValue])
  } else if (this[PromiseStatus] === PromiseStatusMap.rejected &amp;&amp; onrejected) {
    onrejected(this[PromiseValue])
  }
}

catch(onrejected) {
  if (this[PromiseStatus] === PromiseStatusMap.rejected &amp;&amp; onrejected) {
    onrejected(this[PromiseValue])
  }
}
</code></pre>
<p>先不考虑 状态为 <code>pending</code> 的 <code>P</code>，<code>then</code> 和 <code>catch</code> 函数很简单。</p>
<h2 id="完善-promise">完善 Promise</h2>
<p><code>Promise</code> 的特点是可以链式调用，每次调用 <code>then</code> 和 <code>catch</code> 函数都会返回一个新的 <code>Promise</code>，如果 <code>Promise</code> 的 <code>then</code> 或 <code>catch</code> 中传入的回调函数为 <code>undefined</code> 或者 <code>null</code>，则将 <code>Promise</code> 的值向后传递。</p>
<p>修改过后的 <code>then</code> 和 <code>catch</code> 如下：</p>
<pre><code class="language-js">then(onfulfilled, onrejected) {
  if (this[PromiseStatus] === PromiseStatusMap.resolved &amp;&amp; onfulfilled) {
    return new P(resolve =&gt; {
      resolve(onfulfilled(this[PromiseValue]));
    });
  } else if (this[PromiseStatus] === PromiseStatusMap.resolved) {
    return new P(resolve =&gt; {
      resolve(this[PromiseValue]);
    });
  } else if (this[PromiseStatus] === PromiseStatusMap.rejected &amp;&amp; onrejected) {
    return new P(resolve =&gt; {
      resolve(onrejected(this[PromiseValue]));
    });
  } else if (this[PromiseStatus] === PromiseStatusMap.rejected) {
    return new P((_, reject) =&gt; {
      reject(this[PromiseValue]);
    });
  }
}

catch(onrejected) {
  if (this[PromiseStatus] === PromiseStatusMap.resolved) {
    return new P(resolve =&gt; {
      resolve(this[PromiseValue]);
    });
  } else if (this[PromiseStatus] === PromiseStatusMap.rejected &amp;&amp; onrejected) {
    return new P(resolve =&gt; {
      resolve(onrejected(this[PromiseValue]));
    });
  } else if (this[PromiseStatus] === PromiseStatusMap.rejected) {
    return new P((_, reject) =&gt; {
      reject(this[PromiseValue]);
    });
  }
}
</code></pre>
<p>但是当 <code>P</code> 的状态为 <code>pending</code> 时，如何返回一个新的 <code>P</code> 呢？</p>
<p>只需要将 <code>onfulfilled</code>、<code>onrejected</code>，和新返回的 <code>P</code> 的 <code>resolve</code> 和 <code>reject</code> 保存下来，待当前 <code>P</code> 的 <code>resolve</code> 或 <code>reject</code> 调用时，将值向后传递。</p>
<pre><code class="language-js">then(onfulfilled, onrejected) {
  if (this[PromiseStatus] === PromiseStatusMap.pending) {
    return new P((resolve, reject) =&gt; {
      this.thenList.push({
        resolve,
        reject,
        onfulfilled,
        onrejected
      });
    });
  } else if (this[PromiseStatus] === PromiseStatusMap.resolved &amp;&amp; onfulfilled) {
    return new P(resolve =&gt; {
      resolve(onfulfilled(this[PromiseValue]));
    });
  } else if (this[PromiseStatus] === PromiseStatusMap.resolved) {
    return new P(resolve =&gt; {
      resolve(this[PromiseValue]);
    });
  } else if (this[PromiseStatus] === PromiseStatusMap.rejected &amp;&amp; onrejected) {
    return new P(resolve =&gt; {
      resolve(onrejected(this[PromiseValue]));
    });
  } else if (this[PromiseStatus] === PromiseStatusMap.rejected) {
    return new P((_, reject) =&gt; {
      reject(this[PromiseValue]);
    });
  }
}

catch(onrejected) {
  if (this[PromiseStatus] === PromiseStatusMap.pending) {
    return new P((resolve, reject) =&gt; {
      this.catchList.push({
        resolve,
        reject,
        onrejected
      });
    });
  } else if (this[PromiseStatus] === PromiseStatusMap.resolved) {
    return new P(resolve =&gt; {
      resolve(this[PromiseValue]);
    });
  } else if (this[PromiseStatus] === PromiseStatusMap.rejected &amp;&amp; onrejected) {
    return new P(resolve =&gt; {
      resolve(onrejected(this[PromiseValue]));
    });
  } else if (this[PromiseStatus] === PromiseStatusMap.rejected) {
    return new P((_, reject) =&gt; {
      reject(this[PromiseValue]);
    });
  }
}
</code></pre>
<pre><code class="language-js">const reject = reason =&gt; {
  setTimeout(() =&gt; {
    if (this[PromiseStatus] === PromiseStatusMap.pending) {
      this[PromiseValue] = reason;
      this[PromiseStatus] = PromiseStatusMap.rejected;
      this.thenList.forEach(t =&gt; {
        if (t.onrejected) {
          t.resolve(t.onrejected(this[PromiseValue]));
        } else {
          t.reject(this[PromiseValue]);
        }
      });
      this.catchList.forEach(c =&gt; {
        if (c.onrejected) {
          c.resolve(c.onrejected(this[PromiseValue]));
        } else {
          c.reject(this[PromiseValue]);
        }
      });
      this.thenList = [];
      this.catchList = [];
    }
  });
};

const resolve = value =&gt; {
  if (value &amp;&amp; value.then &amp;&amp; typeof value.then === 'function') {
    value.then(resolve, reject);
    return;
  }
  setTimeout(() =&gt; {
    if (this[PromiseStatus] === PromiseStatusMap.pending) {
      this[PromiseValue] = value;
      this[PromiseStatus] = PromiseStatusMap.resolved;
      this.thenList.forEach(t =&gt; {
        if (t.onfulfilled) {
          t.resolve(t.onfulfilled(this[PromiseValue]));
        } else {
          t.resolve(this[PromiseValue]);
        }
      });
      this.catchList.forEach(c =&gt; {
        c.resolve(this[PromiseValue]);
      });
    }
  });
};
</code></pre>
<p><code>Promise</code> 的传入的回调函数如果出现同步异常也会 <code>rejected</code>，如果 <code>Promise</code> <code>reject</code> 的值后面没有 <code>catched</code>，需要抛出 <code>UnCatchedError</code></p>
<pre><code class="language-js">class UnCatchedError extends Error {
  constructor(message) {
    super(message);
    this.name = 'UnCatchedError';
  }
}
</code></pre>
<pre><code class="language-js">try {
  executor(resolve, reject);
} catch (e) {
  if (e instanceof UnCatchedError) {
    throw e;
  }
  return new P((_, reject) =&gt; {
    reject(e);
  });
}
</code></pre>
<h2 id="精简">精简</h2>
<p>将重复的逻辑提炼一下，最后如下：</p>
<pre><code class="language-js">const PromiseStatus = '[[PromiseStatus]]';
const PromiseValue = '[[PromiseValue]]';

const PromiseStatusMap = {
  pending: 'pending',
  resolved: 'resolved',
  rejected: 'rejected'
};

class UnCatchedError extends Error {
  constructor(message) {
    super(message);
    this.name = 'UnCatchedError';
  }
}

class P {
  constructor(executor) {
    this[PromiseStatus] = PromiseStatusMap.pending;
    this[PromiseValue] = undefined;
    this.nextList = [];

    const reject = reason =&gt; {
      setTimeout(() =&gt; {
        if (this[PromiseStatus] === PromiseStatusMap.pending) {
          this[PromiseValue] = reason;
          this[PromiseStatus] = PromiseStatusMap.rejected;
          if (!this.nextList.length) {
            throw new UnCatchedError(reason);
          }
          this.nextList.forEach(t =&gt; {
            if (t.onrejected) {
              t.resolve(t.onrejected(this[PromiseValue]));
            } else {
              t.reject(this[PromiseValue]);
            }
          });
          this.nextList = [];
        }
      });
    };
    const resolve = value =&gt; {
      if (value &amp;&amp; value.then &amp;&amp; typeof value.then === 'function') {
        value.then(resolve, reject);
        return;
      }
      setTimeout(() =&gt; {
        if (this[PromiseStatus] === PromiseStatusMap.pending) {
          this[PromiseValue] = value;
          this[PromiseStatus] = PromiseStatusMap.resolved;
          this.nextList.forEach(t =&gt; {
            if (t.onfulfilled) {
              t.resolve(t.onfulfilled(this[PromiseValue]));
            } else {
              t.resolve(this[PromiseValue]);
            }
          });
          this.nextList = [];
        }
      });
    };
    try {
      executor(resolve, reject);
    } catch (e) {
      if (e instanceof UnCatchedError) {
        throw e;
      }
      return new P((_, reject) =&gt; {
        reject(e);
      });
    }
  }

  _then(onfulfilled, onrejected) {
    if (this[PromiseStatus] === PromiseStatusMap.pending) {
      return new P((resolve, reject) =&gt; {
        this.nextList.push({
          resolve,
          reject,
          onfulfilled,
          onrejected
        });
      });
    } else if (this[PromiseStatus] === PromiseStatusMap.resolved &amp;&amp; onfulfilled) {
      return new P(resolve =&gt; {
        resolve(onfulfilled(this[PromiseValue]))
      });
    } else if (this[PromiseStatus] === PromiseStatusMap.resolved) {
      return new P(resolve =&gt; {
        resolve(this[PromiseValue]);
      });
    } else if (this[PromiseStatus] === PromiseStatusMap.rejected &amp;&amp; onrejected) {
      return new P(resolve =&gt; {
        resolve(onrejected(this[PromiseValue]));
      });
    } else if (this[PromiseStatus] === PromiseStatusMap.rejected) {
      return new P((_, reject) =&gt; {
        reject(this[PromiseValue]);
      });
    }
  }

  then(onfulfilled, onrejected) {
    return this._then(onfulfilled, onrejected);
  }

  catch(onrejected) {
    return this._then(undefined, onrejected);
  }
}
</code></pre>

            </div>
            
              <div class="tag-container">
                
                  <a href="https://zero2key.gitee.io/tag/rY7YvLJW9/" class="tag">
                    Promise
                  </a>
                
                  <a href="https://zero2key.gitee.io/tag/jFL1PqmF8p/" class="tag">
                    手写
                  </a>
                
              </div>
            
            
              <div class="next-post">
                <div class="next">下一篇</div>
                <a href="https://zero2key.gitee.io/post/KVfXkLcIY/">
                  <h3 class="post-title">
                    Redux 源码解析-bindActionCreators
                  </h3>
                </a>
              </div>
            

            
              
                <div id="gitalk-container" data-aos="fade-in"></div>
              

              
            

          </div>

        </div>
      </div>
    </div>

    <script src="https://unpkg.com/aos@next/dist/aos.js"></script>

<script type="application/javascript">

AOS.init();

hljs.initHighlightingOnLoad()

var app = new Vue({
  el: '#app',
  data: {
    menuVisible: false,
  },
})

</script>


  
  
    <script src="https://unpkg.com/gitalk/dist/gitalk.min.js"></script>
    <script>

      var gitalk = new Gitalk({
        clientID: '077888762ae810c37005',
        clientSecret: 'c443e600f8e2687909ccd669db5353cd0839979a',
        repo: 'Zero2key',
        owner: 'Zero2key',
        admin: ['Zero2key'],
        id: location.pathname,      // Ensure uniqueness and length less than 50
        distractionFreeMode: false  // Facebook-like distraction free mode
      })

      gitalk.render('gitalk-container')

    </script>
  

  




  </body>
</html>
